33 视频:高级篇实操总结

你好,我是Chrono。

在“高级篇”的这段时间里,我们学习了PersistentVolume、PersistentVolumeClaim、StatefulSet等API对象,具备了部署有状态应用的能力,然后还学习了管理运维应用和集群的多种方式,包括滚动更新、资源配额、检查探针、名字空间、系统监控等等。

掌握了这些知识,现在的你再回想一下三个月前学习第一节课的时候,有没有发现其实Kubernetes也没有当初自己想象得那么高深莫测呢?

今天也是我们课程的最后一节正课,还是会用视频的形式,把“高级篇”里的一些重要的部分都实际演示出来,结合前面的文字和图片,你可以再次加深对Kubernetes的印象。

接下来就一起开始我们的学习吧。


  1. PV和PVC ———-

我们先来创建一个本地存储卷,也就是PV。

在Master和Worker节点的“/tmp”目录里,先建立一个“host-10m-pv”的目录,表示一个只有10MB容量的存储设备:

mkdir /tmp/host-10m-pv

然后我们使用YAML来定义这个PV对象:

vi host-path-pv.yml

它的kind是PersistentVolume,名字是“host-10m-pv”,后面“spec”里的字段都很重要,描述了PV的基本信息。

  • 因为这个PV是我们手动管理的,“storageClassName”的名字你可以任意起,这里我写的是“host-test”。
  • “accessModes”定义了存储设备的访问模式,用的是最简单的“ReadWriteOnce”,可读可写,但只能被这个节点的Pod挂载。
  • “capacity”定义了存储的容量,因为是测试,就设置成了10MB。注意,定义存储容量使用的是国际标准单位,必须要写成Ki/Mi/Gi的形式,否则就会出错。
  • 最后一个字段“hostPath”指定了存储卷的本地路径,也就是刚才节点上创建的目录“/tmp/host-10m-pv/”。

现在我们用 kubectl apply 创建PV对象:

kubectl apply -f host-path-pv.yml

再用 kubectl get 查看状态:

kubectl get pv

就可以看到Kubernetes里已经有这个存储卷了。它的容量是10MB,访问模式是RWO,状态显示的是“Available”,StorageClass是我们自己定义的“host-test”。

接下来我们再来定义PVC对象:

vi host-path-pvc.yml

它的名字是“host-5m-pvc”,“storageClassName”名字是“host-test”,访问模式是“ReadWriteOnce”,在“resources”字段里向Kubernetes申请使用5MB的存储。

PVC比较简单,不像PV那样包含磁盘路径等存储细节。我们还是用 kubectl apply 创建对象:

kubectl apply -f host-path-pvc.yml

再用 kubectl get 查看PV和PVC的状态:

kubectl get pv,pvc

就会发现已经成功分配了存储卷,状态是“Bound”。虽然PVC申请的是5MB,但系统里只有一个10MB的PV可以用,没有更合适的,所以Kubernetes也只能把它俩绑定在一起。

下面要做的就是把这个PVC挂载进Pod里了,来看这个YAML文件:

vi host-path-pod.yml

它在“volumes”里用“persistentVolumeClaim”声明了PVC的名字“host-5m-pvc”,这样就把PVC和Pod联系起来了

后面的“volumeMounts”就是挂载存储卷,这个你应该比较熟悉了吧,用name指定volume的名字,用path指定路径,这里就是挂载到了容器里的“/tmp”目录。

现在我们创建这个Pod,再查看它的状态:

kubectl apply -f host-path-pod.yml
kubectl get pod -o wide

可以看到它被Kubernetes调到到了worker节点上,让我们用 kubectl exec 进入容器,执行Shell命令,生成一个文本文件:

kubectl exec -it host-pvc-pod -- sh
echo aaa > /tmp/a.txt

然后我们登录worker节点,看一下PV对应的目录“/tmp/host-10m-pv”:

cd /tmp/host-10m-pv
ls 
cat a.txt

输出的内容刚好就是我们在容器里生成的数据,这就说明Pod的数据确实已经持久化到了PV定义的存储设备上。

  1. NFS网络存储卷 ————

下面我们来看看在Kubernetes里使用NFS网络存储卷的用法。

NFS服务器和客户端、还有NFS Provisioner的安装过程我就略过了,你可以对照着[第25讲]一步步来。

安装完成之后,可以看一下Provisioner的运行状态:

kubectl get deploy -n kube-system | grep nfs
kubectl get pod -n kube-system | grep nfs

注意一定要配置好NFS Provisioner里的IP地址和目录,如果地址错误或者目录不存在,那么Pod就无法正常启动,需要用 kubectl logs 来查看错误原因。

来看一下NFS默认的StorageClass定义:

vi class.yaml

它的名字是“nfs-client”,这个很关键,我们必须在PVC里写上它才能够找到NFS Provisioner。

现在我们来看PVC和Pod的定义:

vi nfs-dynamic-pv.yml

这个PVC申请的是10MB,使用的访问模式是“ReadWriteMany”,这是因为NFS是网络共享存储,支持多个Pod同时挂载。

在Pod里我们还是用“persistentVolumeClaim”声明PVC,“volumeMounts”挂载存储卷,目标还是容器里的“/tmp”目录。

使用 kubectl apply 应用这个YAML,就会创建好PVC和Pod,用 kubectl get 查看一下集群里的PV和PVC:

kubectl get pv,pvc

就可以看到,NFS Provisioner自动为我们创建一个10MB的PV,不多也不少。

我们再去NFS服务器上查看共享目录,也会找到PV的存储目录,名字里第一部分是名字空间default,第二部分是这个PVC的名字。在这个目录生成一个文本文件:

echo aaa > a.txt

然后我们再用 kubectl exec 进入Pod,看看它里面的“/tmp”目录:

kubectl exec -it nfs-dyn-pod -- sh
cd /tmp
ls
cat a.txt

会发现NFS磁盘上的文件也出现在了容器里,也就是说共享了网络存储卷。

  1. 创建使用NFS的对象 ————–

掌握了PV、PVC、NFS的用法之后,我们就可以来实验StatefulSet的用法了,创建一个使用NFS存储的对象。

看一下StatefulSet对象的YAML描述文件:

vi redis-pv-sts.yml
  • 第一个重要的字段,是“serviceName”,指定了StatefulSet的服务名是“redis-pv-svc”,它也必须要和后面定义的Service对象一致。
  • 第二个重要字段是“volumeClaimTemplates”,相当于把PVC模板定义直接镶嵌进了对象里,storageClass还是“nfs-client”,申请了100MB的存储容量。
  • 后面的字段都比较简单,和Deployment完全一样,比如replicas、selector、containers。

StatefulSet对象下面是它配套的Service定义,关键是这个“clusterIP: None”,不给Service分配虚IP地址,也就是说它是一个“Headless Service”。外部访问StatefulSet不会经过Service的转发,而是直接访问Pod。

使用 kubectl apply 创建这两个对象,“有状态应用”就运行起来了:

kubectl apply -f redis-pv-sts.yml

kubectl get 来查看StatefulSet对象的状态:

kubectl get sts,pod

这两个有状态的Pod名字是顺序编号的,从0开始分别被命名为 redis-pv-sts-0redis-pv-sts-1,运行的顺序是:0号Pod启动成功后,才会启动1号Pod。

我们再用 kubectl exec 进入Pod内部:

kubectl exec -it redis-pv-sts-0 -- sh

看它的hostname,和Pod名字是一样的:

hostname

再来用试验一下两个Pod的独立域名,应该都可以正常访问:

ping redis-pv-sts-0.redis-svc
ping redis-pv-sts-1.redis-svc

使用命令 kubectl get pvc 来查看StatefulSet关联的存储卷状态:

kubectl get pv,pvc

可以看到StatefulSet使用PVC模板自动创建了两个PV并且绑定了。

为了验证持久化存储的效果,我们用 kubectl exec 运行Redis客户端,添加一些KV数据:

kubectl exec -it redis-pv-sts-0 -- redis-cli
set a 111
set b 222
quit

然后删除这个Pod:

kubectl delete pod redis-pv-sts-0

StatefulSet会监控Pod的运行情况,发现数量不对会重新拉起这个Pod。我们再用Redis客户端登录去检查一下:

kubectl exec -it redis-pv-sts-0 -- redis-cli
get a
get b
keys *

就可以看到,Pod挂载了原来的存储卷,自动恢复了之前添加的Key-Value数据。

  1. 滚动更新 ——–

现在我们来学习一下Kubernetes里滚动更新的用法。

这里有一个V1版本的Nginx应用:

vi ngx-v1.yml

注意在“annotations”里我们使用了字段“kubernetes.io/change-cause”,标注了版本信息“v1, ngx=1.21”,使用的镜像是“nginx:1.21-alpine”。

它后面还有一个配套的Service,比较简单,用的是NodePort,就不多解释了。

然后我们执行命令 kubectl apply 部署这个应用:

kubectl apply -f ngx-v1.yml

用curl发送HTTP请求,看看它的运行信息:

curl 192.168.10.210:30080

从curl命令的输出中可以看到,现在应用的版本是“1.21.6”。

执行 kubectl get pod,看名字后的Hash值,就是Pod的版本。

再来看第二版的对象“ngx-v2.yml”:

vi ngx-v2.yml

它也是在“annotations”里标注了版本信息,镜像升级到“nginx:1.22-alpine”,还添加了一个字段“minReadySeconds”来方便我们观察应用更新的过程

现在执行命令 kubectl apply,因为改动了镜像名,Pod模板变了,就会触发“版本更新”,kubectl rollout status 来查看应用更新的状态:

kubectl apply -f ngx-v2.yml
kubectl rollout status deploy ngx-dep

再执行 kubectl get pod,就会看到Pod已经全部替换成了新版本,用curl访问Nginx,输出的信息也变成了“1.22.0”:

kubectl get pod
curl 192.168.10.210:30080

命令 kubectl describe 可以更清楚地看到Pod的变化情况,是两个同步进行的扩容和缩容动作:

kubectl describe deploy ngx-dep

那我们再来查看更新历史,命令是 kubectl rollout history

kubectl rollout history deploy ngx-dep

可以看到在“CHANGE-CAUSE”列里有刚才两个版本的更新信息。

我们最后用 kubectl rollout undo 来回退到上一个版本:

kubectl rollout undo deploy ngx-dep

再来看更新历史 kubectl rollout history,会发现又变成了最初的版本,用curl发请求试一下:

curl 192.168.10.210:30080

Nginx又恢复成了第一版的1.21.6,我们的版本回退也就成功了。

  1. 水平伸缩 ——–

接下来看Kubernetes的水平自动伸缩功能,也就是对象“HorizontalPodAutoscaler”。

水平自动伸缩功能要求必须有Metrics Server插件,它的安装过程我就不演示了,来直接看看它的运行状态,使用 kubectl get pod

kubectl get pod -n kube-system

可以看到有一个Metrics Server Pod正在运行。

然后我们看一下当前的系统指标:

kubectl top node
kubectl top pod -n kube-system

确认Metrics Server 运行正常,下面我们就可以试验水平自动伸缩功能了。

首先我们来定义一个Deployment对象,部署1个Nginx实例:

vi ngx-hpa-dep.yml

注意在YAML里我们必须要用“resources”字段明确写出它的资源配额,否则HorizontalPodAutoscaler无法获取Pod的指标,也就无法实现自动化扩缩容。

kubectl apply 创建对象后,用 kubectl get pod,可以看到它现在只有一个实例。

接下来的HPA对象很简单,它指定Pod数量最多10个,最少2个,CPU使用率指标设是5%。使用命令 kubectl apply 创建HPA,它会发现Nginx实例只有1个,不符合的下限的要求,就会扩容到2个:

kubectl get pod
kubectl get hpa

然后我们启动一个http Pod,用里面的压力测试工具ab来给Nginx增加流量压力:

kubectl run test -it --image=httpd:alpine -- sh

向Nginx发送一百万个请求,持续30秒,再用 kubectl get hpa 来观察HorizontalPodAutoscaler的运行状况:

ab -c 10 -t 30 -n 1000000 'http://ngx-hpa-svc/'


kubectl get hpa

你可以看到HPA会通过Metrics Server不断地监测Pod的CPU使用率,超过设定值就开始扩容,一直到数量上限。

  1. Prometheus ————–

我们来看看CNCF的二号项目Prometheus。

首先从GitHub上下载它的源码包,最新的版本是0.11,然后解压缩得到部署所需的YAML文件。

然后我们修改 prometheus-service.yamlgrafana-service.yaml 这两个文件,把Service类型改成NodePort,这样就可以直接用节点的IP地址访问。为了方便,我们还把Prometheus的节点端口指定成了“30090”,Grafana的节点端口指定成了“30030”。

记得还要修改 kubeStateMetrics-deployment.yamlprometheusAdapter-deployment.yaml,因为它们里面的镜像放在了gcr.io上,拉取很困难,改成docker hub上的会容易一些

修改完之后,执行两个 kubectl create 命令来部署Prometheus。先是“manifests/setup”目录,创建名字空间等基本对象,然后才是“manifests”目录:

kubectl create -f manifests/setup
kubectl create -f manifests

Prometheus的对象都在名字空间“monitoring”里,创建之后可以用 kubectl get 来查看状态:

kubectl get pod -n monitoring

再来看一下它们的Service对象:

kubectl get svc -n monitoring

端口就是刚才我们设置的“30090”和“30030”。

Prometheus启动之后,我们在浏览器里输入节点的IP地址,再加上端口号“30090”,就会看到Prometheus的Web界面。

可以在这个查询框里任意选择指标,或者使用PromQL编辑表达式,生成可视化图表,比如“node_memory_Active_bytes”这个指标,就当前正在使用的内存容量。

再来看Grafana,访问节点的端口“30030”,就会出来Grafana的登录界面,默认的用户名和密码都是“admin”

Grafana内部预置了很多仪表盘,我们可以在菜单栏的“Dashboards - Browse”里随意挑选,比如选择“Kubernetes / Compute Resources / Namespace (Pods)”这个仪表盘。

  1. Dashboard ————-

现在我们来在Kubernetes集群里部署仪表盘插件Dashboard。

这里使用的是2.6.0版,只有一个YAML文件,来大概看一下:

vi dashboard.yaml
  • 所有的对象都属于“kubernetes-dashboard”名字空间。
  • Service对象使用的是443端口,它映射了Dashboard的8443端口。
  • Dashboard使用Deployment部署了一个实例,端口号是8443。
  • 容器启用了Liveness探针,使用HTTPS方式检查存活状态。

使用命令 kubectl apply 就可以一键部署Dashboard:

kubectl apply -f dashboard.yaml
kubectl get pod -n kubernetes-dashboard

接下来我们给Dashboard配一个Ingress入口,用反向代理的方式来访问它。

先要用openssl工具生成一个自签名的证书,然后把生成的证书和私钥都转换成Secret对象。因为这操作命令比较长,敲键盘很麻烦,这里写成了脚本文件。

openssl req -x509 -days 365 -out k8s.test.crt -keyout k8s.test.key \
  -newkey rsa:2048 -nodes -sha256 \
    -subj '/CN=k8s.test' -extensions EXT -config <( \
       printf "[dn]\nCN=k8s.test\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:k8s.test\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth")

export out="--dry-run=client -o yaml"
kubectl create secret tls dash-tls -n kubernetes-dashboard --cert=k8s.test.crt --key=k8s.test.key $out > cert.yml

证书的参数是有效期365天,私钥是RSA2048位,摘要算法是SHA256,Secret对象的名字是“dash-tls”。

然后我们来看Ingress Class和Ingress的定义:

vi ingress.yml

注意它们也都在名字空间“kubernetes-dashboard”里。Ingress要在“annotations”字段里指定后端目标是HTTPS服务,“tls”字段指定域名“k8s.test”和证书Secret对象“dash-tls”。

再来定义Ingress Controller,镜像用的是“nginx-ingress:2.2-alpine”,注意一定要把“args”里的Ingress Class设置成刚才的“dash-ink”,Service对象也改成NodePort,端口号是30443

最后我们还要给Dashboard创建一个用户,admin-user:

vi admin.yml

这些YAML都准备好了,让我们用 kubectl apply 来逐个创建对象:

kubectl apply -f cert.yml
kubectl apply -f ingress.yml
kubectl apply -f kic.yml
kubectl apply -f admin.yml

在用浏览器访问Dashboard之前,我们要先获取用户的Token,它是以Secret的方式存储的:

kubectl get secret -n kubernetes-dashboard
kubectl describe secrets -n kubernetes-dashboard admin-user-token-xxxx

把这个Token拷贝一下,确保能够解析“k8s.test”这个域名,在浏览器里输入网址“https://k8s.test:30443”,我们就可以登录Dashboard了。

课下作业

不知道今天的参考视频有没有帮你解决一点实操上的小问题,如果你成功做完了所有项目,欢迎在留言区交流经验和新想法,如果遇到了困难,也欢迎描述清楚发上来,我们一起讨论。